import * as THREE from 'three';
import Stats from 'stats.js';

const VERTEX_SHADER = `
  varying vec2 vUv;
  void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
`;

const FRAGMENT_SHADER = `
  uniform sampler2D map;
  uniform vec3 fogColor;
  uniform float fogNear;
  uniform float fogFar;
  varying vec2 vUv;
  void main() {
    gl_FragColor = texture2D(map, vUv);
    gl_FragColor.w *= pow(gl_FragCoord.z, 10.0);
    gl_FragColor = mix(gl_FragColor, vec4(fogColor, gl_FragColor.w), 0.8);
  }
`;

export class App3D {
  private scene: THREE.Scene;
  private camera: THREE.PerspectiveCamera;
  private renderer: THREE.WebGLRenderer;
  private animationId: number | null = null;
  private width: number;
  private height: number;
  private cld1: THREE.Mesh | null = null;
  private cld2: THREE.Mesh | null = null;
  private mouseX = 0;
  private mouseY = 0;
  private wwh = 0;
  private whh = 0;
  private cloudTexture: THREE.Texture;
  private stats: Stats | null = null;

  constructor(canvas: HTMLCanvasElement) {
    this.width = canvas.clientWidth;
    this.height = canvas.clientHeight;
    this.wwh = this.width / 2;
    this.whh = this.height / 2;

    // Scene
    this.scene = new THREE.Scene();

    // Camera
    this.camera = new THREE.PerspectiveCamera(
      40,
      this.width / this.height,
      1,
      5000
    );
    this.camera.position.z = 6000;

    // Renderer
    this.renderer = new THREE.WebGLRenderer({ canvas, antialias: false, alpha: true });
    this.renderer.setSize(this.width, this.height, false);

    // Load textures and build scene
    const loader = new THREE.TextureLoader();
    this.cloudTexture = loader.load('/images/ulap.png', () => {
      this.initClouds();
    });


    // Mouse events
    window.addEventListener('mousemove', this.onMouseMove, false);

    // Stats.js
    // this.stats = new Stats();
    // if (this.stats) {
    //   this.stats.showPanel(0); // 0: fps
    //   document.body.appendChild(this.stats.dom);
    // }
  }

  private onMouseMove = (event: MouseEvent) => {
    this.mouseX = event.clientX - this.wwh;
    this.mouseY = event.clientY - this.whh;
  };

  private initClouds() {
    // Shader material for clouds
    const cloudMaterial = new THREE.ShaderMaterial({
      uniforms: {
        map: { value: this.cloudTexture },
        fogColor: { value: new THREE.Color(0xffffff) },
        fogNear: { value: 10 },
        fogFar: { value: 2800 },
      },
      vertexShader: VERTEX_SHADER,
      fragmentShader: FRAGMENT_SHADER,
      depthWrite: false,
      depthTest: false,
      transparent: true,
    });

    // Build merged cloud planes as a single BufferGeometry
    const plane = new THREE.PlaneGeometry(80, 80);
    const planePos = plane.getAttribute('position');
    const planeUv = plane.getAttribute('uv');
    const planeIndices = plane.index!;
    const numPlanes = 6000;
    const positions = new Float32Array(numPlanes * planePos.count * 3);
    const uvs = new Float32Array(numPlanes * planeUv.count * 2);
    const indices = new Uint32Array(numPlanes * planeIndices.count);

    let posOffset = 0;
    let uvOffset = 0;
    let idxOffset = 0;
    let vertOffset = 0;
    for (let i = 0; i < numPlanes; i++) {
      // Random transform for each cloud
      const px = Math.random() * 1500 - 750;
      const py = -Math.random() * 200 - 40;
      const pz = i;
      const rotZ = Math.random() * Math.PI;
      const scale = Math.random() * Math.random() * 3 + 0.8;
      const mat = new THREE.Matrix4()
      .makeTranslation(px, py, pz)
      .multiply(new THREE.Matrix4().makeRotationZ(rotZ))
      .multiply(new THREE.Matrix4().makeScale(scale, scale, 1))
      // Add transformed vertices
      for (let j = 0; j < planePos.count; j++) {
        const v = new THREE.Vector3().fromBufferAttribute(planePos, j);
        v.applyMatrix4(mat);
        positions[posOffset++] = v.x;
        positions[posOffset++] = v.y;
        positions[posOffset++] = v.z;
        uvs[uvOffset++] = planeUv.getX(j);
        uvs[uvOffset++] = planeUv.getY(j);
      }
      // Add indices
      for (let j = 0; j < planeIndices.count; j++) {
        indices[idxOffset++] = vertOffset + planeIndices.getX(j);
      }
      vertOffset += planePos.count;
    }
    const buffGeom = new THREE.BufferGeometry();
    buffGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    buffGeom.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
    buffGeom.setIndex(new THREE.BufferAttribute(indices, 1));
    buffGeom.computeBoundingSphere();

    this.cld1 = new THREE.Mesh(buffGeom, cloudMaterial);
    this.cld1.position.z = 0;
    this.scene.add(this.cld1);
    this.cld2 = this.cld1.clone();
    this.cld2.position.z = -6000;
    this.scene.add(this.cld2);
  }
  

  private animate = () => {
    if (this.stats) this.stats.begin();
    const timer = Date.now();
    // Camera follows mouse
    if (this.mouseY === 0) {
      this.camera.position.y += Math.sin(timer * 0.0005) * 4;
    }
    if (this.cld1 && this.cld2) {
      if (this.cld1.position.z < 6000) {
        this.cld1.position.z += 2;
      } else {
        this.cld1.position.z = 0;
      }
      if (this.cld2.position.z < 0) {
        this.cld2.position.z += 2;
      } else {
        this.cld2.position.z = -6000;
      }
    }
    this.camera.position.x = this.mouseX * 0.1;
    this.camera.position.y = -this.mouseY * 0.15;
    this.renderer.render(this.scene, this.camera);
    if (this.stats) this.stats.end();
    this.animationId = requestAnimationFrame(this.animate);
   
  };

  public start() {
    if (!this.animationId) {
      this.animate();
    }
  }

  public stop() {
    if (this.animationId) {
      cancelAnimationFrame(this.animationId);
      this.animationId = null;
    }
    if (this.stats && this.stats.dom && this.stats.dom.parentNode) {
      this.stats.dom.parentNode.removeChild(this.stats.dom);
      this.stats = null;
    }
  }

  public resize(width: number, height: number) {
    this.width = width;
    this.height = height;
    this.wwh = width / 2;
    this.whh = height / 2;
    this.camera.aspect = width / height;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(width, height, false);
  }
} 